use crate::Point::Point;
use crate::Polygon::Polygon;

pub struct SimplifyTool {}

impl SimplifyTool {
    pub fn simplify(poly: Polygon, tolerance: f64, highest_quality: bool) -> Polygon {
        let sq_tolerance = tolerance * tolerance;
        let mut poly_sim = if highest_quality {
            poly
        } else {
            Self::simplify_radial_dist(poly, sq_tolerance)
        };
        poly_sim = Polygon::new(Self::simplify_douglas_peucker(&poly_sim, sq_tolerance));
        poly_sim
    }

    fn get_sq_seg_dist(p: [f64; 2], p1: [f64; 2], p2: [f64; 2]) -> f64 {
        let mut x = p1[0];
        let mut y = p1[1];
        let dx = p2[0] - x;
        let dy = p2[1] - y;

        if dx != 0.0 || dy != 0.0 {
            let t = ((p[0] - x) * dx + (p[1] - y) * dy) / (dx * dx + dy * dy);

            if t > 1.0 {
                x = p2[0];
                y = p2[1];
            } else if t > 0.0 {
                x += dx * t;
                y += dy * t;
            }
        }

        let dx = p[0] - x;
        let dy = p[1] - y;
        dx * dx + dy * dy
    }

    fn simplify_radial_dist(poly: Polygon, sq_tolerance: f64) -> Polygon {
        let mut prev_point = poly.vertices[0];
        let mut new_points = vec![prev_point];

        for i in 1..poly.vertices.len() {
            let point = poly.vertices[i];

            if point.distance_to_point_squared(&prev_point) > sq_tolerance {
                new_points.push(point);
                prev_point = point;
            }
        }

        if let Some(point) = poly.vertices.last() {
            if prev_point != *point {
                new_points.push(*point);
            }
        }

        Polygon::new(new_points)
    }

    fn simplify_dp_step(
        poly: &Polygon,
        first: usize,
        last: usize,
        sq_tolerance: f64,
        simplified: &mut Vec<Point>,
    ) {
        let mut index = 0;
        let mut max_sq_dist = sq_tolerance;

        for i in first + 1..last {
            let sq_dist = Self::get_sq_seg_dist(
                poly.vertices[i].to_array(),
                poly.vertices[first].to_array(),
                poly.vertices[last].to_array(),
            );

            if sq_dist > max_sq_dist {
                index = i;
                max_sq_dist = sq_dist;
            }
        }

        if max_sq_dist > sq_tolerance {
            let v_idx = poly.vertices[index].clone();
            if index - first > 1 {
                Self::simplify_dp_step(poly, first, index, sq_tolerance, simplified);
            }
            simplified.push(v_idx);
            if last - index > 1 {
                Self::simplify_dp_step(poly, index, last, sq_tolerance, simplified);
            }
        }
    }

    fn simplify_douglas_peucker(poly: &Polygon, sq_tolerance: f64) -> Vec<Point> {
        let last = poly.vertices.len() - 1;
        let mut simplified = vec![poly.vertices[0]];
        Self::simplify_dp_step(poly, 0, last, sq_tolerance, &mut simplified);
        simplified.push(poly.vertices[last]);
        simplified
    }
}
